{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1bc1785a",
   "metadata": {},
   "source": [
    "## 外部控制编辑图表状态\n",
    "\n",
    "我们讨论了人类外部控制的动机：<br/>\n",
    "(1) 批准 - 我们可以中断Agent，向用户显示状态，并允许用户接受操作。<br/>\n",
    "(2) 调试 - 我们可以倒回图以重现或避免问题。<br/>\n",
    "(3) 编辑 - 可以修改状态。<br/>\n",
    "\n",
    "我们展示了断点如何支持用户批准，但还不知道一旦图形中断，如何修改图形状态。<br/>\n",
    "\n",
    "### 目标\n",
    "现在，让我们展示如何直接编辑图状态并插入人工反馈。<br/>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95a38f63",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langgraph langchain_openai"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1bae3f69",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OPENAI_API_KEY: ········\n",
      "OPENAI_BASE_URL: ········\n"
     ]
    }
   ],
   "source": [
    "import os, getpass\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "\n",
    "_set_env(\"OPENAI_API_KEY\")\n",
    "_set_env(\"OPENAI_BASE_URL\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0904486f",
   "metadata": {},
   "source": [
    "## 编辑状态\n",
    "之前，我们引入断点。<br/>\n",
    "我们使用它们来中断图形并等待用户批准，然后再执行下一个节点。<br/>\n",
    "但断点也是修改图形状态的机会。<br/>\n",
    "让我们在“助手”节点之前为我们的代理设置一个断点。<br/>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8450ebb9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "def multiply(a: int, b: int) -> int:\n",
    "    \"\"\"将 a 和 b 相乘。\n",
    "\n",
    "    参数:\n",
    "        a: 第一个整数\n",
    "        b: 第二个整数\n",
    "    \"\"\"\n",
    "    return a * b\n",
    "\n",
    "# 这将是一个工具\n",
    "def add(a: int, b: int) -> int:\n",
    "    \"\"\"将 a 和 b 相加。\n",
    "\n",
    "    参数:\n",
    "        a: 第一个整数\n",
    "        b: 第二个整数\n",
    "    \"\"\"\n",
    "    return a + b\n",
    "\n",
    "def divide(a: int, b: int) -> float:\n",
    "    \"\"\"将 a 除以 b。\n",
    "\n",
    "    参数:\n",
    "        a: 第一个整数\n",
    "        b: 第二个整数\n",
    "    \"\"\"\n",
    "    return a / b\n",
    "\n",
    "tools = [add, multiply, divide]\n",
    "llm = ChatOpenAI(model=\"gpt-4o\")\n",
    "llm_with_tools = llm.bind_tools(tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e6e51cdd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAANgAAAEjCAIAAADfYFjUAAAQAElEQVR4nOydB1hTVxvHTzYQIOwNMkUBBRRFxT2r4p511FFH66Tuaq2ram0VrYhatdaq/dzgqnXUvQeKCjJEEJC9QxKy870Qi2gBUUlyQs7vycNzczfJP+86555DVygUiEDQNHREIGAAESIBC4gQCVhAhEjAAiJEAhYQIRKwQCuFKCqTFWSKBaUyQalUKlVIxVpQgWLpU+lMioER3cCYZu2ohwhvo01C5HMlzx/yk2N43AKJkRnDwIgG36uxGQNpQylULkM5L0WCUj6DRU2LF7j4sF2bwcsQESqgaEVBWy5T3DpVkJ8pMrdjuvoY2rvrI21GKJClxPBfPRdkJgvbBZt7+BshnUcLhBh7p+TKkbx2/cz9O5uihgWY9lunC0QCWc+xNvqGNKTD4C7EK0dy9QyobfpaoIZLfpboeHjGZ+NsHDwMkK6CtRAv7M+xcdFrFsRBOkBkeEaHQRYWdiykk+ArxONbM9z9DH3a6YQKlUSGv2oWZAL/NdI9qAhLrh/Pc/Zi65QKgUHTHe78XVCUI0a6B45CTHhYSmdQ/TqbIN1j9CKny0dydbBvHo5CvHokr0VXXVQhQKFQwBVArQrpGNgJMeqfIp8gY5a+7tYyWnQ1fXaXK+TLkC6BlxDBJaUlCNoFN+RiTV3oONgy+mox0iXwEmLyUz60ySKdx8nTIOZWCdIl8PrWoeELGmGRelm0aNGJEyfQh9OjR4+MjAykAqCVxcSCmfWyDOkMeAmxOE/i2kzdQnz27Bn6cLKysoqKipDKaBxgmJ4oQDoDRkKE8LwoV6y6NOXmzZtTp05t3779wIEDly1blp+fDysDAgIyMzNXrVrVuXNneMvj8bZv3z5u3Djlbhs3bhQKhcrDu3XrduDAgcmTJ8MhV69e7devH6wcMGDA3LlzkQpgG9PzX+lQQREjIfK5Uvj0kWqIj4+fPXt2q1atjh49umDBgsTExOXLl6MKdcLfpUuXXrlyBRYOHjy4Z8+esWPHbtq0Cfa/cOHCjh07lGdgMBiRkZGenp7h4eFBQUGwA6wEn75hwwakAuCjgA8E6QwY9Ufkc2VsY1WZw+joaD09vYkTJ1KpVBsbGy8vr6SkpP/uNmbMGLB8Li4uyrePHz++devWrFmzUEWFj8PhzJs3D6kFNofGL9GhCg5GQlTIFUyVpcx+fn7gZENCQgIDAzt27Ojo6Age9r+7gdm7ffs2OG4wmVJpuUEyMzOr3AryReqCRqcw9XSogIDRv2pgTC/JkyDV0KRJk82bN1taWoaFhQ0aNGjatGlg7f67G2wFXww7HD9+/MGDBxMmTKi6lclkInXBK5aCFpHOgJEQwS+Dd0Yqo127dhALnjp1CqLDkpISsI5Km1cJlNOPHTs2YsQIECK4b1hTWlqKNIRKAxUMwckiGtHNbBhyuUra+6OioiDagwUwisHBwZDqgsigBFN1H4lEUlZWZmVlpXwrFouvXbuGNIRIILN01KG+iXhFIXoGNGhcQSoAHDEkyxEREVD8i4mJgewYFGlra8tisUB5d+7cAUcMeYyzs/PJkydfvXpVXFy8cuVKiCy5XC6fX80twZ7wF9JqOBtSAQlRpbbO2v1ozgeBlxCdvdkvY1UiREiHweGuX78emkOmTJnCZrMhFqTTy3M1SKXv378PNhLM4Zo1ayC5Hjp0KBQRW7duPWPGDHjbvXt3qDW+c0IHBwcoJULREcJKVN/IpIqMpDKnJjr05ABePbTLeNLz+3MGfGWPdJuUWF56YlnHQZZIZ8DLIuob0k2tmY91rOPJf7l1skDXeqdj94B9UD+LXxe98O1UfcdYmUwGBedqN0FuAVVAKDv/d5Orq+vu3buRathTQbWbDA0Noc2w2k1Qkty6dWu1m+IfcK0c9cys1VcqwgEcH56KvlpMoSh8O1b/FHNNJRWRSASZR7WbQJ2gCaQa4LrwG6h2E6yvqfQIiRHEqdVuOr0rs9NQSyMTBtIlMH2KD74M7zYc9XcJ0zg6+49j2ogUPMnuWkReQbYI6RKXDuXaOOvpoAoRzs81Q9PzoQ3pHQdb2rnpRDnt8uFcBw99nR0HB99mdQqVMnK+0+0zBXH3uKhBI5cpIsMzzGyYujwakxYMwnTrdH5anKBdP4sGWeC9f74w4UFp52GWujzwDdKWYenyMkS3TuWzjengpiGE0mdrfW+A3HRhWoLgwfkiv84mrT8zo1J1qKNNtWiHEJW8ei4A45ESw7d0ZHEsGKBLeBkY0+RyhD9UioJbKOWXyBRIEX+/FO7c3ZfdvKMJg0meWixHm4RYSVZKWX6GmM+VwotKoQh49dl5TCAQpKamNm3aFNUrRqZ0+KTZHJqRGcPBTZ/NIaOXv4VWClGlxMXFrV69ev/+/YigRsjvkoAFRIgELCBCJGABESIBC4gQCVhAhEjAAiJEAhYQIRKwgAiRgAVEiAQsIEIkYAERIgELiBAJWECESMACIkQCFhAhErCACJGABUSIBCwgQiRgAREiAQuIEAlYQIRIwAIiRAIWECG+C4VCsbTUocGrMYEI8V0UCkVeXh4iqBciRAIWECESsIAIkYAFRIgELCBCJGABESIBC4gQCVhAhEjAAiJEAhYQIRKwgAiRgAVEiAQsIEIkYAERIgELiBAJWEAm/HnN559/LhAI5HK5RCIpKiqysbGBZbFYfO7cOURQPWQiuNf07t07q4L8/HyZTJaRkQHLhoaGiKAWiBBfM3LkyEaNGlVdQ6FQOnXqhAhqgQjxNUwmc8CAATTamwl4nZychg0bhghqgQjxDcOHD3dwcFAugzns0qWLra0tIqgFIsQ3gFEcPHgwnV5eSQA3TcyhOiFCfAswinZ2dlQqtXPnztbW1oigLuqhjigRyQoyJQJ+fc7erUH6dZt05cqVIP/ByTF8pP1QqYhjwTC1YiK8+dQ64qVDuUmPeeY2LAaLGFccYXPomS8EbGNa844m7r74VqM+ySKe2J5p78H+fKEVIuCNXK64+GcmhYLcmmOqxY+3iGd2Z4EKXZsbI4KWcG7Pq9afmTl5GiD8+Eh/mpEkoFApRIXaRdv+VtFXixGWfKQQC7LFDCYNEbQKYzNmeoJAJsOxd8FHClFQKuNY4p6IEf6LrYt+cZ4E4cdHJisyCfyqSLcd7UPAlVIhZ8EP0h+RgAVEiAQsIEIkYAERIgELiBAJWECESMACIkQCFhAhErCACJGABUSIBCwgQiRgAelWjZKTk7p0C3jy5BEiaA4iRGRiYvrF2ElWVja17JOS8mLkqGD0aQwa0iMzKwMRqoO4ZmRmZj5h/Fe175OQ+Ax9GtnZWcXFRYhQA1osxNu3r1+6fO7J00dcbknTJj5jx07y9wtQbrpz9+ahQ3vjE2LNzCx8fHynTJppbm5R03pwzV9OHvnLxp3Nm/uX8kp/37P97p0bRcWFno29unfv3bfPQFizd98uOBw8+LSvvxk2dHRNl448fnjf/l2bQncsW7Hg5ctkV1d32PmzXv0eRT+YM7dc66PHDAgK6vTDyg2I8Dba6pqFQuHqtd+JRKJFC1esWb3Jycl5yXffFBYWwKbE5/HfLp7t799qz+6js2YuePEicd1Py2tZX5WfflrxLPZJSMi3sE/Tpj4bN62NjX0C9nLkiC+srW0uX3wAwqrl0gwGg8cr3Rz20/y5Sy/9c79Tx+4//bwyJycbZLp29SbY4c/9J4gKq0VbLaKent6uHQf19fU5HBN4C2bpxMmjT2OiO3XsFvM0GraOGT2RSqWCepp4eiWnJME+Na2vyuMnD0FzrQLawPKUyTM7derOMTap+6XhrUQiGffFFC+vZrDcq2cwWNOkpAS4HCLUiha7ZoGAv+u3LdGPowoK8pVrlEGYTzM/MFrfLgkJaBnYtm1HB3tHpd+saX1VmjXzO3xkf0lJsW/zFq1atfVs3PSDLq2kSRNv5YKRUfnDZWAjEeF9aKtrBn83+5tJYH6WLllz/uztC+fuVG5q7NHkx7WbLcwtd+wMG/vFoHnzp8XEPK5lfVUWLlg+dMio+w9uL1k6Z/CQHrt/3yaVSut+aSUULPviY462WsQrVy+IxWKI0sBForcNEhDYuh28ILaLirp7LOLA4iUhEccu0On0atdXPdDYyBh89+hRE0Cj129c3rf/N0NDo+HDxtT90oSPQ1uFCOkqOD6lFICr1y5WboqOjhKJRSA4CwvLXr2CbWzsQuZMyc7Jys/LrXZ95YEl3JKLF8/26T0AokDw0fCC8A5SnLpfmvDRaKtrdnX1gPjs5Klj4Drv3rv18OE9SB1yc7NhU0zs4+UrFpw6HQG26llcTETkQVCejbVtTesrz0mn0f/Yu2P5yoVgDiELPn/+r+dJ8c18/GCTg4MTXO7GjSvp6am1XLoWHJ2c4e+VKxfg0ojwH7TVInbr2is1NXnvvp1QYYEkF2K7g4f2/u/AntJS7ozp80BqW8LXh25cw2Qyu3bptTF0B/hl8LDVrq88J5vNXrn857Dwn2fO/hLeuri4fTU1pPdn/WG5TWB7UOTSZfMgIx4/bkpNl25cQ3ID2Ns5QEERkmgfb9+Nob8iwtt85Ng3N0/m0xh073YmiKBVnAhP7fulnak1A2EGaeIjYAERIgELiBAJWECESMACIkQCFhAhErCACJGABUSIBCwgQiRgAREiAQuIEAlYoCYhHo3YY2hohAgqwNrK2t+vPdJy1CREGxvr1q3bIIIKaBj9wdUkxKB2PRFBNSjkUqT9qEmIFERmB1IVFGpD+GxJskLAAiJEAhYQIRKwgAiRgAVEiAQsIEIkYAERIgELiBAJWECESMACIkQCFhAhErCACJGABUSIBCwgQiRgAabjI95/cGfg4O617PDkyaPnSQlI9Zw7d7r0wwfBjo6Oqv3+qyIUCpevWNilW8DOXVuQroKpEFsFtDke8U8tO/wStk4qkSAVU1RUuGXrerYBG30gCYnPmjb1qePODx/ei4l9fOHcncmTZiBdhbZ8+XL04aQnCKg0qpWjHlINM2d/KZVKPT29ps+cUFCQv237xuMnj9y+c8PLq5mRkfG0GeNTUpLS0l9aW9sWFxX+sGbJocP7Tp2OyM3NbubjR6PR7t67teS7b+ITYvft29WjR9/Z30xOS3u5a9cWPp+Xl5/73dK5QwaPVF5o5KhgeztHc3PLnp+1ZbFYhw/v37otNCXlRfPmLXJyskLmTJbL5Xfv3ezQviuTyaz7/UdEHDQzNd/1W3jYlp/j4mOdnJyVMw6Fha8P37rh7NlTly+ft7d3tLKyOfP3ifBtoXDPt+9c796td9TDexs2/HD8xOGTJ4/K5HLlBAXwIVTev4+P739PUvcbS7hf0riFkb4hdn1pMY0Rk5ISpn09R6FQgODMzSzW/7zN0NDw2yUh586dmjD+q+C+g+B72hS6QyQSjZswZNTnE/r0HlBayl2ydI6+vsGY0RNfpacWFRaMGDbW1dUdzpaWmtLIyeXX7fthGdxfY48myqtwS7k5K0B9GQAAEABJREFUOdkg99TUZHjr4uz2+chxJSXFE74c3qyZH5zT17elCcf0669Cqt7bylXfXr7y1hDwzs6uv/92uOqaxMQ4B8dGoeu3w/LadcuOHNm/ZPEPJ04ejYuLWbN6k4O9I3j8RYtnHTtyHq5y8eLZtm07DB0y6unT6NVrvvtx7eYmnl6gvFkhk0Bn4Byq3n+1J4GfENJycHTNqakpoDAPd8+MjHRYmDdvKagQ1oMvZrHKbXDSi0R3d09YAEMI9qB/vyF0Ot3U1Kxli9bJyc+VOwS2aa9UIUiNx+eNHj1ReXLY5PGvEJ8/jwdDZWZmDuFmQMvANm3Kn4XjcEwcHJyUcwXA78HdrfE7t/f90rWXLz6o+npHhSDlzKyMb2Z/C6eCl1fTZnA2gUCwc1fYxAlfg4Bgn+7de/P5/JyKoeRBtR7u5be087ctA/oPBRXCMhhRN1cPuIGq91/tSd47fLdWgKNFhC8GNATaik945uriblwxbQ4QHx87dOhoVKGPrl16wcLjx1FgRSDMrzwWRFl+hudx476Y8vqohFg3Nw97OwflWzgWbE/lslKUL14kens3rzxJYUE+CAhiA/DRlaqtO+CL4f4rZ5sqLMw3NubAtUA08xdMr7qnoaFRVnYm6AysMlwuJubx9GlzK7cWlxTBgVXvv6aTIO0HRyGWG60KCwEWy+1fg5SfnwdfmDIDgPVTJ8+CBbFEPG/ud337DKx6OCShIKDGHq/HVQdZu7t5Kpch3CwsLKg0ck9jopVuGixi966fKVfm5uZkZL7y928FtwEuz6liNoCqvNc1JyQ8s7S0rnwLnjQ4eLBILAJpHvzf6XfOdu36JTs7Bz09PbhtCEVYzNdOtoRbAp4BQt5z509X3n9NJ2kA4OiaQWdKOwQGoHEVN2plZQ3WERQJ35mNjR2sBHsZFXUXbIlMJgNx7PnjV+WekOfa2LyetwKEWHmSsjIB/KVSy/9rMLdwLFwIjoVI9MnT1xOH7923E3y0na19enoq+H3lzlV5r2sGe/wy5YWy6APJR05udseO3SAAhZ+BctaW7OysXzavg/NX/R9Bi40audy7fwuW4T8KDV3dwr8V/Ayq3n9NJ2kA4GgRQUkQBqG3Pezzf90oOE1LSyvIdrdv3Tdp0gzIJYeN6A1ZJ2TQi79dhZTKqzLNBLi2sWMmKZch+Bs2dPSixbMhs4EFsEAuLu6QFsDhLVq0Hj6yDyigdet2C+cvQxXfembmqyHDeh09fLbus5pBlv30yaOvvgr5ctIIBoNpYWG5ds0vHGMObFq1Yj3kInAqiOrGj5vq6NhI+X/5ePsqj4UdtmzdcOLEEagMgHYHDxr5zv3D2ao9SQOATG+BLlw4c+LU0S2bdyMdQKentwAz878De95ZCbEapKvvrGSzDSsrfGoDYkFw8YigUdQhRMh/vxg7CeEKpMxBQZ0RQaOQTg9o/c9bEUHTECESsIAIkYAFRIgELCBCJGABESIBC4gQCVhAhEjAAiJEAhYQIRKwgAiRgAVEiAQsIEIkYAERIgELPlKIemyaXNEgpt7SMTgWTBqWxucjn1nhmDNyXgoQQasQC2VZKWXG5th1z0YfLUSHxvoCngwRtIrsl2WeAZg+e/qRQtQzoPl3Mrn4ZyYiaAkl+aJ7Z/I6DbFEWPKRD08peflMcPVYnncQx8JGj8UmeQ+OUKmKwhwxr1gSe6t4zCInOhPTYbc+SYhAcZ740ZXigkwxv7ieJ2uVSKVUCoVGU/dwQTK5XCwW6+upanypD6VMKPyUmzGxZlIoyMFDv0VXU4QxnypEFXHw4EEKhTJixAikdlatWnX16tUffvihTRssZjq/fft2YWFh3759UYMGUyFqimfPni1dujQ1NTUoKOiXX35BeJCRkWFqampgYIAaLthFDElJSXv37kUaAiwxqBCVj1+TcPPmTYQH9vb2LBZr3LhxqOGClxDT09PBDn3xxRdIE8TFxT18+FC5nJ+ff+DAAYQNECvPnz8fn99GvUNc8xvAKf/999+Vbw0NDSFSbN++PcKGkpIS+IW4ubmhBgdGFnH16tVCoRBpCIgOK82hEi6Xu2/fPoQTHA7HwcFhwIABqMGBixDnzJkzatQoPc0VTfbs2ZOVlVXVP0DaDpEiwgwIFsPDw2NjY1HDgrjmd4FIEWzz/v37EcZIpVKIFzt16oQaCpq3iH/99dfdu3cR4UOg0+lQ5uzcuTNqKGhYiJGRkQUFBYGBgYjwgYCPPnXqFHx6qEGg4QbiQYMGIcLHYmRU3pXmjz/+gIJX3ce0xRONWcS0tLRdu3YhwicDhe4G4KM1I0QohkGT7qRJ+I7eqV1A4ziqmE4BaS2aEaKFhcXOnTsRoV4JCwuD2ifSTjQgxHXr1kELASLUN9AGuGDBAqSdqFuIixcvHjhwILQQIIIK2L69fPY/iHyQtqFuIa5Zs8bT0xMRVMnGjRtzcnKQVqE+If7999/Xr19HBNUDLUNQ00FahZqEeObMGWjJ7dChAyKoBWWwiGFbeU2oSYh9+vSZOHEiIqiXbdu2ZWZqx5OWKhdidnb25s2bEUETbNq06caNG1rRr0W1QuTxeIsWLZo1axYiaIjhw4eDEPHv2q1aIRoaGu7ZswcRNAqVSj106BDmPlqFQgwNDc3Ly0NaiFRaz89oaxyIjpQPhWGLqoS4ZcsWX19fS0tMB7iohadPn/bv3x81ONq2bXv27Flsa92q6gY2Y8YMpIWAC4uNjV25ciVqiBw8eNDe3h4a+hF+qEqI8oqBO/SwGbijLuzevRsMRkNVIdC7d29sfZQKn1kJDAyEZI1O147BmSCKotFo06dPRwRNoMJkpV27domJiUgbWLt2LYfDafAq1MUYEVU0vSNtYOnSpZBXDR06FDV0cI4RVWgRy8rKiouLEd7MmTMH0kldUCHCO0ZUoRChgjplyhSEMVOnTh0wYAC0gyPdYMSIETY2NghLVChENzc3BoOBbXF49OjR8DtpSM+ovxecY0TVNvH9+eefeGbNgwYNgtCwZcuWSJeAGDErKwthiWpVAv82k8k0NzdHONGzZ89du3Y5OTkhHUNH64jo3+FE8CkRQ5wAvvjkyZO4/TYIqnXN4PvwGYGgpKQkKCjo4sWLOqtC3Y0RIUdbsWIFwgAIEiAuBPOsXa2O9QvOMaLKe2g/efJE408xJyUlTZ48+dKlS0i30d0YEdiwYYOtre2oUaOQhoiOjoYWvEOHDiECxqjcInbt2lX9k/ZUcvPmzbCwMKJCJTjHiOoYMTY4OBiuwuVyeTzeo0ePkLq4cOHCqVOnyKNblYwfP37u3LnNmjVD+KGqOuKXX34ZExMjk8lAgpWJMzS3R0VFqaeMfPz48Tt37hAVVkVHY0TIUtPT06uugWAxMjJSDW0t0KKTnJwMbSeIoCWoMEacMWOGiYlJ5Vu5XO7j46MGFf766685OTlEhf9FR+uI3bp1g+iwUnmwoIaxskNDQyESmDNnDiL8B92tI4aEhPj7+4MthGVTU1Nvb2+kSlatWmVtbY153zMNotN1RIlEMmzYsLS0NA8PD5WWURYtWtSmTZuBAwcighZSp4hNKpGX8eToI6EsWfjDsmXLWjQPKi1SVd/E75d+32dAtx49eiBCzUCMGBAQgOejAu+xiHH3uE+ulxRmiw0MNVaUfi9yhYLJlhdlKlx82C26mti66CNCFfz8/CgVoIp53SBSgi+9SZMmEDIibKjNIt47X5ifKekw2MbIjIGwBz7ckjzJlWM57fqaN2rakOfY/lCaNm2akJBApb7OB6Chi81m4zalQ43Jyt2zhSV50g6DrLVChajit25ixQye7Ah3nhonQIR/GT58OIvFqrrG2dm5e/fuCCeqF2JRrjg/Q9Qm2AppId1G2z66XIQI/wItC1W7o4M5hLY+hBnVCxFUCC1zSDthsmjFeRJuoQQR/mX06NGVRtHV1bVLly4IM6oXIq9EZumoxR1IHT3ZRblEiG/o37+/g4MDqjCH48aNQ/hRvRAlIrlE+NH1Gs3DK5YoZGQe6rcAo8hgMMAc4jlxn3aMkKRrpMbzoeYq4MrEZXJhmQzVBwYosLP3TC8vr38O1M8ULGxjulymgL9sY5qNi56R6ScltUSIGJHwgJv4iJ/6jG/X2FgiUdDoNCqDTqHU23fUuk35mBalfFQv8IUUqVgqTxMr5ApuRL4+m+bux/ZuZ2zI+RhFEiFiwfNHpdePF5jasWkstncPS62bfNnKA5WVitJTBM/uZbp4GbQfaE5nfFg3BiJEDSOTKf76LZtfihx8bZn6Wvx16Bux4GXhYlqYXrLj25TOwyy9Ao3rfjgRoibJTRce2fTKLdDO2JGFGgpmjhx4Pb2dl5ch6jS4rp19NDaDPYFbIDnze653d4jzG44KK7H2tCzIo0G8Ucf9iRA1Q/bLsojwLOdW9qjhYubEyc1Gf/9epySdCFEDyKSKiLCMhq1CJeaNTEr51AcX3t/iSoSoAU7tynZr64B0Ays3s5QEcXrie7qhECGqm9jbJQI+hcXWjj5N9YKhpdHVY+95aIsIUd3cPFUIRgLpEvrGLAqdDrXSWvbBSIjLVyycN38aatCAOTRvZERnYdrdPfrpP/OWBvL49d+JzsLVLPYOr5Yd6k2IkccPr123DBFqJe4+j8XWxXHxWAaMwmxxUY64ph3qTYgJCc8QoVYkInleutDQXEcfqWFbGCQ/rdEo1k/LSsicKY8fP4SF8+f/+nX7/sYeTdLSXm765cfE53E0Gt3Z2XX8uKn+fgHKnW/evPrH3h2paSkcjom7u+fsmQutrd+dc+HO3ZuHDu2NT4g1M7Pw8fGdMmmmuTmOz559EC/j+BYuRkhl3H94+vb9yKycJFtrd79m3Tu0Halss953aDFClBa+nx2KWCkSCRo5Nuvba0YjRx/lUafPhj14fIbFNPBv3svKQoXjihtZGmSn1Rgm1o9F3BS6o2lTn549+16++ABUWFRUOGPmBCsrmx2//i887HdTE7NVPywWCMoT+AdRd79fPh/2PHzwzLKlP+bkZG3a/OM7Z0t8Hv/t4tn+/q327D46a+aCFy8S1/20HGk/JXkSuVRVvRkePj53KHKVg53n4jmRvXt8fe3WwRNnXs/8RaXSU9OfRkX/PfurPWu+v0pnMA9GvB7V/Na9Y7fuHR3cd/7sqb+bm9pduPwbUhkMFj0ruaymrSpJVo4c/ZPJYs2b+52drb2Dg9P8ed+XlQlOnDwCm3b/vq1jh65Dh4wCc+jt3Xza13Pu3LkR/7Zbj3karaenN2b0RLCUga3bbfh52+efj0faD69YRmOqKk25F3XCtZH/4H4LjAzNPFwDenWbcvPukVJeoXIrGMIRg74zN7MHB9Wiea+8/FRYA+tv3D7c3Ltbc5+uBgbGrVoEu7sGIJXB0KML+TX2rVSJEJNTkjw8mlSOesNmsx0dGiUmxpVvSn7epMmbgUc8G3vB3/j42KqH+zTzEwqF3y4JAce2byEAAAXfSURBVEG/ykgHyVa6da1GwJOpKF+Wy+UpaU8ae7wZWgi0qFDIU15GK99aWTqzWK8fsdXTKw8PBGVchUKRX5hubeVSeZSDXROkSlhsGp9b/SMcKul9U1iQb2/vWHWNnr6+oEzA4/FEIhGL9SZtNDAo/3QEgrf6aoJz/3Ht5mvXLu7YGbZ128aWLVpDiAmRItJyVDe4i1QqlskkZ//ZDq+q60v5ry0ihVKNxRGK+HK5rFKgAJOp2kRKIVPU1NVSJUI0YLOFImHVNWUCgYO9k3JEf6HwTaDAr5Cgudm7iQh4ZHhNGP9VVNTdYxEHFi8JiTh2QVumfq4JQw4tL69++v2/A5OpB9lGS78+zb27Vl0PvriWo/RYbCqVJpG8+aZEYhU+Dw4GWCyUGxhV/yWqxDWDw42Li5FIXhthbikXcmQXFzdQkmfjprGxTyr3VC67unlUPTw6OuruvVuofIRZy169gqdPm1vKK83OwXQ8tbpjaEIHy4VUg51t4zJhqbtrS+XL2am5kZG5Cce6lkPAOJma2L5Me1q5Ji7hJlIZUpFMj11jZFJvQgRfDOJ7+Og+pMz9+g3h83kbQlfn5GS/fJm89sfv9Vh6fXqXj9M1aOCIGzevHDt2ANT5KPrB1m2hLfxbebh7Vj1VTOzj5SsWnDodUVxc9CwuJiLyICjSxtoWaTkmlgw6TVXPRvbp8XVM3NW7USfL48XU6P2Hl/z6+3Rw2bUf5evT/emzy9CgAsuXru9NfRWDVIa4TGrrWqPrrzdn16/vYEhH5i+Yvu7HsICWgcu+/3Hfvl0jRwVDqgGVnV827YKUBZXPg9c3Lz/30JF9W7ZugKQ4oGWbyZNmvHOq4cPGgAS3hK8P3biGyWR27dJrY+gObffLgLM3++wf2RauKimIujTy++brvZeu/fHX+S1icRkUCyeM/pnBeE+X2+6dJvD5RcfPbADhwhn69w7535HvVTRSIT+f79G8xvupfjSwe+cKxULk21lb2+YvHcj07cCBLx5hRmR4Jt3YyMhCF8eIenErfWiIPce8+m5HpPeNWmnS2lDEEyHdQ8gTWziwalIhIg9PqZmmrYxvn35pbG3I1K/+K4mNv37g2PJqNxnoG0Pxr9pNgS0H9PtsFqonIMT8bf/cajdBuQcqQdWWYNq2Gty353RUA/nJhe37maCaIUJUNx0Gmt+/WGTnXf1Iax6ureZM21ftJpGojMWqPthnMuvT10OwWNM91AKLVWMgxC8SMhgKZ6/aIiUiRHXj4W/0PJovLBVV+/AeVATNmHZI05iZ1uc9CItKuwx7T4pGYkQN0GeCTfK9TLlcJ4aJyknM8/TXt3rf4HJEiJrh8wVOyXdeoYZOzvMCS1uqTzvOe/ckQtQMplbMUQvtn99Ik0m1ePi/2sl7UeDmxeg6vE7jDhMhagwDQ8aIuQ6gRX5RGWpYyKXyjJhs58b0gO6mdTyECFGTGJsxvlrnxpDzXz3OKuM2kPpiXkpRwrW09n1NWvX8gAYRkjVrnp5jrNMTBdci81mGLCqTaWzJxvYxv1rgFZTx8gXcXJ5vR5Nh09zQB0KEiAWOjQ1GL3RKfcZPjOYn38swtdUXC+V0Jp3GpFNwdVpUKlUilMgkMqSQF2WVQV7s1ZLt1cb5Q0dGVEKEiBGNvNiNKqq+OWnCiqGLpUKBXCTANJvRYyMqjc42ZhkY021dbBjMT/rFECHiiLWTnrUKn6fDkeqFyNSjyJG2zrMCsE0YVJoW378OUr05NTJl5KVqcU0hLY5nZsNEBO2heiFaObK0bTjxN5TxpBb2LEMTEnVoEzVaRHt3vWvHspEW8s/+zFY96lpHJWBCbfM1x94ueR7N8+1kbmrNpNFxL30LBTJuvvjmidzPvrC2ctLFgY60mvdMHJ4Sy4++WpydIqQxsHbVHHMGt1Di7MUO6GEKzbiIoG1Q6vikjKgM67Z5hRzKWqS5UouhKBRk8kSC5iGpJQELiBAJWECESMACIkQCFhAhErCACJGABf8HAAD//yBpaPAAAAAGSURBVAMAr/ey8+GmaPQAAAAASUVORK5CYII=",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import MessagesState\n",
    "from langgraph.graph import START, StateGraph\n",
    "from langgraph.prebuilt import tools_condition, ToolNode\n",
    "\n",
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "\n",
    "# 系统消息\n",
    "sys_msg = SystemMessage(content=\"你是一个负责对一组输入执行算术运算的助手。\")\n",
    "\n",
    "# 节点\n",
    "def assistant(state: MessagesState):\n",
    "   return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n",
    "\n",
    "# 图\n",
    "builder = StateGraph(MessagesState)\n",
    "\n",
    "# 定义节点：这些节点执行工作\n",
    "builder.add_node(\"assistant\", assistant)\n",
    "builder.add_node(\"tools\", ToolNode(tools))\n",
    "\n",
    "# 定义边：这些边决定控制流\n",
    "builder.add_edge(START, \"assistant\")\n",
    "builder.add_conditional_edges(\n",
    "    \"assistant\",\n",
    "    # 如果来自助手的最新消息（结果）是工具调用 -> tools_condition 路由到工具\n",
    "    # 如果来自助手的最新消息（结果）不是工具调用 -> tools_condition 路由到 END\n",
    "    tools_condition,\n",
    ")\n",
    "builder.add_edge(\"tools\", \"assistant\")\n",
    "\n",
    "memory = MemorySaver()\n",
    "graph = builder.compile(interrupt_before=[\"assistant\"], checkpointer=memory)\n",
    "\n",
    "display(Image(graph.get_graph(xray=True).draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4ad1b121",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "7和8相乘等于多少？\n"
     ]
    }
   ],
   "source": [
    "# 输入\n",
    "initial_input = {\"messages\": \"7和8相乘等于多少？\"}\n",
    "\n",
    "# 线程\n",
    "thread = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# 运行图表直到第一次中断\n",
    "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
    "    event['messages'][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "37823a87",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StateSnapshot(values={'messages': [HumanMessage(content='7和8相乘等于多少？', additional_kwargs={}, response_metadata={}, id='bfe88c21-be2a-4c63-bbf7-61a6c4349565')]}, next=('human_feedback',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0315f8-f59e-6491-8000-bef7f66cf1d0'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}, 'thread_id': '1'}, created_at='2025-05-15T07:38:14.517262+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0315f8-f599-65e2-bfff-59187f55347d'}}, tasks=(PregelTask(id='b7ba7484-cf71-f7e7-d11b-8bc434036230', name='human_feedback', path=('__pregel_pull', 'human_feedback'), error=None, interrupts=(), state=None, result=None),))"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "state = graph.get_state(thread)\n",
    "state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "4e7bac6d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'configurable': {'thread_id': '1',\n",
       "  'checkpoint_ns': '',\n",
       "  'checkpoint_id': '1f0315f9-18e1-6f1a-8001-1ef94cc5d4da'}}"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.update_state(\n",
    "    thread,\n",
    "    {\"messages\": [HumanMessage(content=\"啊不，我想说的是把9乘以10\")]},\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "04c62837",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "7和8相乘等于多少？\n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "啊不，我想说的是把9乘以10\n"
     ]
    }
   ],
   "source": [
    "new_state = graph.get_state(thread).values\n",
    "for m in new_state['messages']:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c7318bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "for event in graph.stream(None, thread, stream_mode=\"values\"):|\n",
    "    event['messages'][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "c18d30cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: multiply\n",
      "\n",
      "90\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "9乘以10等于90。\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    event['messages'][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "083af6b9",
   "metadata": {},
   "source": [
    "## 等待用户输入\n",
    "\n",
    "因此，很明显，我们可以在断点后编辑Agent状态。\n",
    "现在，如果我们想允许人工反馈 来执行此状态更新，该怎么办？\n",
    "我们将在Agent中添加一个节点作为人工反馈的占位符。\n",
    "此 human_feedback节点允许用户直接向状态添加反馈。\n",
    "\n",
    "我们使用interrup_before我们的human_feedback节点指定断点。\n",
    "我们设置了一个检查点来保存图形的状态，直到这个节点。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "71211161",
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Failed to reach https://mermaid.ink/ API while trying to render your graph after 1 retries. To resolve this issue:\n1. Check your internet connection and try again\n2. Try with higher retry settings: `draw_mermaid_png(..., max_retries=5, retry_delay=2.0)`\n3. Use the Pyppeteer rendering method which will render your graph locally in a browser: `draw_mermaid_png(..., draw_method=MermaidDrawMethod.PYPPETEER)`",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTimeoutError\u001b[0m                              Traceback (most recent call last)",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\connectionpool.py:534\u001b[0m, in \u001b[0;36mHTTPConnectionPool._make_request\u001b[1;34m(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)\u001b[0m\n\u001b[0;32m    533\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 534\u001b[0m     response \u001b[38;5;241m=\u001b[39m \u001b[43mconn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgetresponse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    535\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (BaseSSLError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m e:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\connection.py:516\u001b[0m, in \u001b[0;36mHTTPConnection.getresponse\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m    515\u001b[0m \u001b[38;5;66;03m# Get the response from http.client.HTTPConnection\u001b[39;00m\n\u001b[1;32m--> 516\u001b[0m httplib_response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgetresponse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    518\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\http\\client.py:1375\u001b[0m, in \u001b[0;36mHTTPConnection.getresponse\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m   1374\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m-> 1375\u001b[0m     \u001b[43mresponse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbegin\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1376\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\http\\client.py:318\u001b[0m, in \u001b[0;36mHTTPResponse.begin\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m    317\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[1;32m--> 318\u001b[0m     version, status, reason \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_read_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    319\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m status \u001b[38;5;241m!=\u001b[39m CONTINUE:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\http\\client.py:279\u001b[0m, in \u001b[0;36mHTTPResponse._read_status\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m    278\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21m_read_status\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m--> 279\u001b[0m     line \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mstr\u001b[39m(\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mreadline\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_MAXLINE\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124miso-8859-1\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m    280\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(line) \u001b[38;5;241m>\u001b[39m _MAXLINE:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\socket.py:717\u001b[0m, in \u001b[0;36mSocketIO.readinto\u001b[1;34m(self, b)\u001b[0m\n\u001b[0;32m    716\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 717\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_sock\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrecv_into\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    718\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m timeout:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\ssl.py:1307\u001b[0m, in \u001b[0;36mSSLSocket.recv_into\u001b[1;34m(self, buffer, nbytes, flags)\u001b[0m\n\u001b[0;32m   1304\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[0;32m   1305\u001b[0m           \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnon-zero flags not allowed in calls to recv_into() on \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m\n\u001b[0;32m   1306\u001b[0m           \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m)\n\u001b[1;32m-> 1307\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnbytes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1308\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\ssl.py:1163\u001b[0m, in \u001b[0;36mSSLSocket.read\u001b[1;34m(self, len, buffer)\u001b[0m\n\u001b[0;32m   1162\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m buffer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m-> 1163\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_sslobj\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1164\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "\u001b[1;31mTimeoutError\u001b[0m: The read operation timed out",
      "\nThe above exception was the direct cause of the following exception:\n",
      "\u001b[1;31mReadTimeoutError\u001b[0m                          Traceback (most recent call last)",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\requests\\adapters.py:667\u001b[0m, in \u001b[0;36mHTTPAdapter.send\u001b[1;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[0;32m    666\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 667\u001b[0m     resp \u001b[38;5;241m=\u001b[39m \u001b[43mconn\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43murlopen\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    668\u001b[0m \u001b[43m        \u001b[49m\u001b[43mmethod\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    669\u001b[0m \u001b[43m        \u001b[49m\u001b[43murl\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    670\u001b[0m \u001b[43m        \u001b[49m\u001b[43mbody\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    671\u001b[0m \u001b[43m        \u001b[49m\u001b[43mheaders\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    672\u001b[0m \u001b[43m        \u001b[49m\u001b[43mredirect\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m    673\u001b[0m \u001b[43m        \u001b[49m\u001b[43massert_same_host\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m    674\u001b[0m \u001b[43m        \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m    675\u001b[0m \u001b[43m        \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m    676\u001b[0m \u001b[43m        \u001b[49m\u001b[43mretries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    677\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    678\u001b[0m \u001b[43m        \u001b[49m\u001b[43mchunked\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    679\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    681\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (ProtocolError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m err:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\connectionpool.py:841\u001b[0m, in \u001b[0;36mHTTPConnectionPool.urlopen\u001b[1;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[0m\n\u001b[0;32m    839\u001b[0m     new_e \u001b[38;5;241m=\u001b[39m ProtocolError(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mConnection aborted.\u001b[39m\u001b[38;5;124m\"\u001b[39m, new_e)\n\u001b[1;32m--> 841\u001b[0m retries \u001b[38;5;241m=\u001b[39m \u001b[43mretries\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mincrement\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    842\u001b[0m \u001b[43m    \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merror\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnew_e\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_pool\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_stacktrace\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msys\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexc_info\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[0;32m    843\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    844\u001b[0m retries\u001b[38;5;241m.\u001b[39msleep()\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\util\\retry.py:474\u001b[0m, in \u001b[0;36mRetry.increment\u001b[1;34m(self, method, url, response, error, _pool, _stacktrace)\u001b[0m\n\u001b[0;32m    473\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m read \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m \u001b[38;5;129;01mor\u001b[39;00m method \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_is_method_retryable(method):\n\u001b[1;32m--> 474\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[43mreraise\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mtype\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43merror\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merror\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_stacktrace\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    475\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m read \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\util\\util.py:39\u001b[0m, in \u001b[0;36mreraise\u001b[1;34m(tp, value, tb)\u001b[0m\n\u001b[0;32m     38\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m value\u001b[38;5;241m.\u001b[39mwith_traceback(tb)\n\u001b[1;32m---> 39\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m value\n\u001b[0;32m     40\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\connectionpool.py:787\u001b[0m, in \u001b[0;36mHTTPConnectionPool.urlopen\u001b[1;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[0m\n\u001b[0;32m    786\u001b[0m \u001b[38;5;66;03m# Make the request on the HTTPConnection object\u001b[39;00m\n\u001b[1;32m--> 787\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_make_request(\n\u001b[0;32m    788\u001b[0m     conn,\n\u001b[0;32m    789\u001b[0m     method,\n\u001b[0;32m    790\u001b[0m     url,\n\u001b[0;32m    791\u001b[0m     timeout\u001b[38;5;241m=\u001b[39mtimeout_obj,\n\u001b[0;32m    792\u001b[0m     body\u001b[38;5;241m=\u001b[39mbody,\n\u001b[0;32m    793\u001b[0m     headers\u001b[38;5;241m=\u001b[39mheaders,\n\u001b[0;32m    794\u001b[0m     chunked\u001b[38;5;241m=\u001b[39mchunked,\n\u001b[0;32m    795\u001b[0m     retries\u001b[38;5;241m=\u001b[39mretries,\n\u001b[0;32m    796\u001b[0m     response_conn\u001b[38;5;241m=\u001b[39mresponse_conn,\n\u001b[0;32m    797\u001b[0m     preload_content\u001b[38;5;241m=\u001b[39mpreload_content,\n\u001b[0;32m    798\u001b[0m     decode_content\u001b[38;5;241m=\u001b[39mdecode_content,\n\u001b[0;32m    799\u001b[0m     \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mresponse_kw,\n\u001b[0;32m    800\u001b[0m )\n\u001b[0;32m    802\u001b[0m \u001b[38;5;66;03m# Everything went great!\u001b[39;00m\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\connectionpool.py:536\u001b[0m, in \u001b[0;36mHTTPConnectionPool._make_request\u001b[1;34m(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)\u001b[0m\n\u001b[0;32m    535\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (BaseSSLError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m--> 536\u001b[0m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_raise_timeout\u001b[49m\u001b[43m(\u001b[49m\u001b[43merr\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43me\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout_value\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mread_timeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    537\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\urllib3\\connectionpool.py:367\u001b[0m, in \u001b[0;36mHTTPConnectionPool._raise_timeout\u001b[1;34m(self, err, url, timeout_value)\u001b[0m\n\u001b[0;32m    366\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(err, SocketTimeout):\n\u001b[1;32m--> 367\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m ReadTimeoutError(\n\u001b[0;32m    368\u001b[0m         \u001b[38;5;28mself\u001b[39m, url, \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRead timed out. (read timeout=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtimeout_value\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m)\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    369\u001b[0m     ) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01merr\u001b[39;00m\n\u001b[0;32m    371\u001b[0m \u001b[38;5;66;03m# See the above comment about EAGAIN in Python 3.\u001b[39;00m\n",
      "\u001b[1;31mReadTimeoutError\u001b[0m: HTTPSConnectionPool(host='mermaid.ink', port=443): Read timed out. (read timeout=10)",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[1;31mReadTimeout\u001b[0m                               Traceback (most recent call last)",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\langchain_core\\runnables\\graph_mermaid.py:430\u001b[0m, in \u001b[0;36m_render_mermaid_using_api\u001b[1;34m(mermaid_syntax, output_file_path, background_color, file_type, max_retries, retry_delay)\u001b[0m\n\u001b[0;32m    429\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 430\u001b[0m     response \u001b[38;5;241m=\u001b[39m \u001b[43mrequests\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[43mimage_url\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m10\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m    431\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m response\u001b[38;5;241m.\u001b[39mstatus_code \u001b[38;5;241m==\u001b[39m requests\u001b[38;5;241m.\u001b[39mcodes\u001b[38;5;241m.\u001b[39mok:\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\requests\\api.py:73\u001b[0m, in \u001b[0;36mget\u001b[1;34m(url, params, **kwargs)\u001b[0m\n\u001b[0;32m     63\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124mr\u001b[39m\u001b[38;5;124;03m\"\"\"Sends a GET request.\u001b[39;00m\n\u001b[0;32m     64\u001b[0m \n\u001b[0;32m     65\u001b[0m \u001b[38;5;124;03m:param url: URL for the new :class:`Request` object.\u001b[39;00m\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m     70\u001b[0m \u001b[38;5;124;03m:rtype: requests.Response\u001b[39;00m\n\u001b[0;32m     71\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m---> 73\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m request(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mget\u001b[39m\u001b[38;5;124m\"\u001b[39m, url, params\u001b[38;5;241m=\u001b[39mparams, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\requests\\api.py:59\u001b[0m, in \u001b[0;36mrequest\u001b[1;34m(method, url, **kwargs)\u001b[0m\n\u001b[0;32m     58\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m sessions\u001b[38;5;241m.\u001b[39mSession() \u001b[38;5;28;01mas\u001b[39;00m session:\n\u001b[1;32m---> 59\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m session\u001b[38;5;241m.\u001b[39mrequest(method\u001b[38;5;241m=\u001b[39mmethod, url\u001b[38;5;241m=\u001b[39murl, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\requests\\sessions.py:589\u001b[0m, in \u001b[0;36mSession.request\u001b[1;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[0;32m    588\u001b[0m send_kwargs\u001b[38;5;241m.\u001b[39mupdate(settings)\n\u001b[1;32m--> 589\u001b[0m resp \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msend(prep, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39msend_kwargs)\n\u001b[0;32m    591\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m resp\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\requests\\sessions.py:703\u001b[0m, in \u001b[0;36mSession.send\u001b[1;34m(self, request, **kwargs)\u001b[0m\n\u001b[0;32m    702\u001b[0m \u001b[38;5;66;03m# Send the request\u001b[39;00m\n\u001b[1;32m--> 703\u001b[0m r \u001b[38;5;241m=\u001b[39m adapter\u001b[38;5;241m.\u001b[39msend(request, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m    705\u001b[0m \u001b[38;5;66;03m# Total elapsed time of the request (approximately)\u001b[39;00m\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\requests\\adapters.py:713\u001b[0m, in \u001b[0;36mHTTPAdapter.send\u001b[1;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[0;32m    712\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(e, ReadTimeoutError):\n\u001b[1;32m--> 713\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m ReadTimeout(e, request\u001b[38;5;241m=\u001b[39mrequest)\n\u001b[0;32m    714\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(e, _InvalidHeader):\n",
      "\u001b[1;31mReadTimeout\u001b[0m: HTTPSConnectionPool(host='mermaid.ink', port=443): Read timed out. (read timeout=10)",
      "\nThe above exception was the direct cause of the following exception:\n",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[23], line 33\u001b[0m\n\u001b[0;32m     31\u001b[0m memory \u001b[38;5;241m=\u001b[39m MemorySaver()\n\u001b[0;32m     32\u001b[0m graph \u001b[38;5;241m=\u001b[39m builder\u001b[38;5;241m.\u001b[39mcompile(interrupt_before\u001b[38;5;241m=\u001b[39m[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhuman_feedback\u001b[39m\u001b[38;5;124m\"\u001b[39m], checkpointer\u001b[38;5;241m=\u001b[39mmemory)\n\u001b[1;32m---> 33\u001b[0m display(Image(\u001b[43mgraph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_graph\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdraw_mermaid_png\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m))\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\langchain_core\\runnables\\graph.py:685\u001b[0m, in \u001b[0;36mGraph.draw_mermaid_png\u001b[1;34m(self, curve_style, node_colors, wrap_label_n_words, output_file_path, draw_method, background_color, padding, max_retries, retry_delay, frontmatter_config)\u001b[0m\n\u001b[0;32m    677\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mlangchain_core\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mrunnables\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgraph_mermaid\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m draw_mermaid_png\n\u001b[0;32m    679\u001b[0m mermaid_syntax \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdraw_mermaid(\n\u001b[0;32m    680\u001b[0m     curve_style\u001b[38;5;241m=\u001b[39mcurve_style,\n\u001b[0;32m    681\u001b[0m     node_colors\u001b[38;5;241m=\u001b[39mnode_colors,\n\u001b[0;32m    682\u001b[0m     wrap_label_n_words\u001b[38;5;241m=\u001b[39mwrap_label_n_words,\n\u001b[0;32m    683\u001b[0m     frontmatter_config\u001b[38;5;241m=\u001b[39mfrontmatter_config,\n\u001b[0;32m    684\u001b[0m )\n\u001b[1;32m--> 685\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mdraw_mermaid_png\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    686\u001b[0m \u001b[43m    \u001b[49m\u001b[43mmermaid_syntax\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmermaid_syntax\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    687\u001b[0m \u001b[43m    \u001b[49m\u001b[43moutput_file_path\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_file_path\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    688\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdraw_method\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdraw_method\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    689\u001b[0m \u001b[43m    \u001b[49m\u001b[43mbackground_color\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbackground_color\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    690\u001b[0m \u001b[43m    \u001b[49m\u001b[43mpadding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpadding\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    691\u001b[0m \u001b[43m    \u001b[49m\u001b[43mmax_retries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    692\u001b[0m \u001b[43m    \u001b[49m\u001b[43mretry_delay\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mretry_delay\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    693\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\langchain_core\\runnables\\graph_mermaid.py:293\u001b[0m, in \u001b[0;36mdraw_mermaid_png\u001b[1;34m(mermaid_syntax, output_file_path, draw_method, background_color, padding, max_retries, retry_delay)\u001b[0m\n\u001b[0;32m    287\u001b[0m     img_bytes \u001b[38;5;241m=\u001b[39m asyncio\u001b[38;5;241m.\u001b[39mrun(\n\u001b[0;32m    288\u001b[0m         _render_mermaid_using_pyppeteer(\n\u001b[0;32m    289\u001b[0m             mermaid_syntax, output_file_path, background_color, padding\n\u001b[0;32m    290\u001b[0m         )\n\u001b[0;32m    291\u001b[0m     )\n\u001b[0;32m    292\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m draw_method \u001b[38;5;241m==\u001b[39m MermaidDrawMethod\u001b[38;5;241m.\u001b[39mAPI:\n\u001b[1;32m--> 293\u001b[0m     img_bytes \u001b[38;5;241m=\u001b[39m \u001b[43m_render_mermaid_using_api\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    294\u001b[0m \u001b[43m        \u001b[49m\u001b[43mmermaid_syntax\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    295\u001b[0m \u001b[43m        \u001b[49m\u001b[43moutput_file_path\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_file_path\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    296\u001b[0m \u001b[43m        \u001b[49m\u001b[43mbackground_color\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbackground_color\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    297\u001b[0m \u001b[43m        \u001b[49m\u001b[43mmax_retries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    298\u001b[0m \u001b[43m        \u001b[49m\u001b[43mretry_delay\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mretry_delay\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    299\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    300\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m    301\u001b[0m     supported_methods \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m, \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mjoin([m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;28;01mfor\u001b[39;00m m \u001b[38;5;129;01min\u001b[39;00m MermaidDrawMethod])\n",
      "File \u001b[1;32mD:\\CacheData\\anaconda\\envs\\python310\\lib\\site-packages\\langchain_core\\runnables\\graph_mermaid.py:462\u001b[0m, in \u001b[0;36m_render_mermaid_using_api\u001b[1;34m(mermaid_syntax, output_file_path, background_color, file_type, max_retries, retry_delay)\u001b[0m\n\u001b[0;32m    457\u001b[0m         \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m    458\u001b[0m             msg \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m    459\u001b[0m                 \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to reach https://mermaid.ink/ API while trying to render \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    460\u001b[0m                 \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myour graph after \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmax_retries\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m retries. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    461\u001b[0m             ) \u001b[38;5;241m+\u001b[39m error_msg_suffix\n\u001b[1;32m--> 462\u001b[0m             \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(msg) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01me\u001b[39;00m\n\u001b[0;32m    464\u001b[0m \u001b[38;5;66;03m# This should not be reached, but just in case\u001b[39;00m\n\u001b[0;32m    465\u001b[0m msg \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m    466\u001b[0m     \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to reach https://mermaid.ink/ API while trying to render \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    467\u001b[0m     \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myour graph after \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mmax_retries\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m retries. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    468\u001b[0m ) \u001b[38;5;241m+\u001b[39m error_msg_suffix\n",
      "\u001b[1;31mValueError\u001b[0m: Failed to reach https://mermaid.ink/ API while trying to render your graph after 1 retries. To resolve this issue:\n1. Check your internet connection and try again\n2. Try with higher retry settings: `draw_mermaid_png(..., max_retries=5, retry_delay=2.0)`\n3. Use the Pyppeteer rendering method which will render your graph locally in a browser: `draw_mermaid_png(..., draw_method=MermaidDrawMethod.PYPPETEER)`"
     ]
    }
   ],
   "source": [
    "# 系统消息\n",
    "sys_msg = SystemMessage(content=\"你是一个负责对一组输入执行算术运算的助手。\")\n",
    "\n",
    "# 应该在其上中断的无操作节点\n",
    "def human_feedback(state: MessagesState):\n",
    "    pass\n",
    "\n",
    "# 助手节点\n",
    "def assistant(state: MessagesState):\n",
    "   return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n",
    "\n",
    "# 图\n",
    "builder = StateGraph(MessagesState)\n",
    "\n",
    "# 定义节点：这些节点执行工作\n",
    "builder.add_node(\"assistant\", assistant)\n",
    "builder.add_node(\"tools\", ToolNode(tools))\n",
    "builder.add_node(\"human_feedback\", human_feedback)\n",
    "\n",
    "# 定义边：这些边决定控制流\n",
    "builder.add_edge(START, \"human_feedback\")\n",
    "builder.add_edge(\"human_feedback\", \"assistant\")\n",
    "builder.add_conditional_edges(\n",
    "    \"assistant\",\n",
    "    # 如果来自助手的最新消息（结果）是工具调用 -> tools_condition 路由到工具\n",
    "    # 如果来自助手的最新消息（结果）不是工具调用 -> tools_condition 路由到 END\n",
    "    tools_condition,\n",
    ")\n",
    "builder.add_edge(\"tools\", \"human_feedback\")\n",
    "\n",
    "memory = MemorySaver()\n",
    "graph = builder.compile(interrupt_before=[\"human_feedback\"], checkpointer=memory)\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cff13774",
   "metadata": {},
   "source": [
    "我们将从用户那里获得反馈。\n",
    "我们使用 .updata_state来更新图的状态，并使用我们得到的人工响应，就像以前一样。\n",
    "我们使用 as_node=\"human_feedback\" 参数将此状态更新应用为指定节点 human_feedback。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "1568bec8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "2和3相乘等于多少？\n",
      "告诉我你想如何更新状态: \n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "你好！让我来帮你计算一下。2和3相乘等于多少呢？\n",
      "Tool Calls:\n",
      "  multiply (call_N8gloAosi8H0XLJRFXCJXBDc)\n",
      " Call ID: call_N8gloAosi8H0XLJRFXCJXBDc\n",
      "  Args:\n",
      "    a: 2\n",
      "    b: 3\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: multiply\n",
      "\n",
      "6\n"
     ]
    }
   ],
   "source": [
    "# 输入\n",
    "initial_input = {\"messages\": \"2和3相乘等于多少？\"}\n",
    "\n",
    "# 线程\n",
    "thread = {\"configurable\": {\"thread_id\": \"5\"}}\n",
    "\n",
    "# 运行图直到第一次中断\n",
    "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()\n",
    "    \n",
    "# 获取用户输入\n",
    "user_input = input(\"告诉我你想如何更新状态: \")\n",
    "\n",
    "# 我们现在更新状态，就像我们是 human_feedback 节点一样\n",
    "graph.update_state(thread, {\"messages\": user_input}, as_node=\"human_feedback\")\n",
    "\n",
    "# 继续图的执行\n",
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "7fc180d7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "啊不，我想说的是把9乘以10\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  multiply (call_d0AtOZVZms94dY6cwqpxvN9q)\n",
      " Call ID: call_d0AtOZVZms94dY6cwqpxvN9q\n",
      "  Args:\n",
      "    a: 9\n",
      "    b: 10\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: multiply\n",
      "\n",
      "90\n"
     ]
    }
   ],
   "source": [
    "# 继续执行图\n",
    "for event in graph.stream(None, thread, stream_mode=\"values\"):\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80d11600",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.10",
   "language": "python",
   "name": "python310"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
